/* eslint-disable no-bitwise */
/**
 * 默认报告样式
 *
 * Created by snowingsea on 2021/01/19.
 */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const files = require('./../../../files');
const functions = require('./../../../functions');
const handleInV = require('./../../../handle-integration-variables');
const tests = require('./../../../tests');
const projectVersions = require('./../../../project-versions');
const BaseReporter = require('./../base-report');
const classCreator = require('../base-report-test-case-transformer');
const Docx = require('./../adapter/docx');
const { getCountableKinds } = require('../../../file-elements');
const common = require('../../../common');
const utility = require('../../../utility');

const rowsHighWaterMark = (1972 / 135) * 200;

class DefaultReporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null) {
    super(versionId, locale, version, project, creator, outputDir);
    this.docx = new Docx(path.join(__dirname, 'template', 'docx', this.isIntegrationTest() ? '501-it.docx' : '501.docx'));
    this.ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
  }

  overrideGetAreas() {
    const that = this;
    that.ReportTestCaseTransformer.prototype.getAreas = function getAreas() {
      function getLeafs(arr, leafs = []) {
        for (let i = 0; i < arr.length; i += 1) {
          const item = arr[i];
          if (item.children && item.children.length) {
            getLeafs(item.children, leafs);
          } else {
            leafs.push(item);
          }
        }
        return leafs;
      }

      const leafs = getLeafs([
        ...this.paramVariablesData,
        ...this.globalVariablesData,
      ]);

      if (leafs.length && !leafs.find((l) => l.data && l.data['@value'])) {
        leafs[utility.randomNum(0, leafs.length - 1)].data['@value'] = '0';
      }
      // 格式化处理局部静态变量名
      if (that.isIntegrationTest()) {
        [this.staticsData, this.staticsOutputData].forEach((item) => {
          if (!item) {
            return;
          }
          item.forEach((variable) => {
            if (variable.originalName && variable.originalName !== variable.name) {
              variable.originalName = variable.name;
            }
          });
        });
      }
      return [
        this.paramVariablesData,
        this.globalVariablesData,
        this.staticsData,
        this.stubsData,
        this.returnValueData,
        this.globalOutputData,
        this.staticsOutputData,
      ];
    };
  }

  /**
   * 报告生成
   */
  generate() {
    const result = {
      cachePath: this.outputDir,
    };
    this.overrideGetAreas();
    return new Promise((resolve, reject) => {
      this.spliteByFile()
        .then((files) => files.filter((file) => file))
        .then((files) => this.generateSummary(files))
        .then(() => {
          const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}.${this.format}.zip`;
          result.reportPath = path.join(this.outputDir, fileName);
          const reportsDir = path.join(this.outputDir, this.project.projectName);
          return compressing.zip.compressDir(reportsDir, result.reportPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  spliteByFile() {
    return new Promise((resolve, reject) => {
      const result = {};
      this.spliteByProjectVersion((versionDir) => fs.ensureDir(versionDir))
        .then(({ projectDir, versionPath }) => {
          result.projectDir = projectDir;
          result.versionPath = versionPath;
          result.versionDir = versionPath;
          return files.getFiles(this.versionId, {}, { kinds: files.kind.file });
        })
        .then((resFiles) => resolve(this.handleFiles(resFiles, result.versionDir)))
        .catch(reject);
    });
  }

  /**
   * 生成测试报告总览
   * @returns {Promise}
   */
  async generateSummary(files) {
    const summary = { reportTime: moment().format('YYYY/MM/DD HH:mm:ss') };
    summary.overview = await projectVersions.getOverview(this.versionId);
    const testResultOverview = await projectVersions.testResultOverview(this.versionId);
    const testsOverview = {
      unknown: 0, // 未执行
      succeed: 0,
      failed: 0,
      noResult: 0,
      exception: 0,
      codeDefect: 0,
    };
    Object.keys(testResultOverview).forEach((key) => {
      Object.keys(testResultOverview[key]).forEach((testResultKind) => {
        testResultKind = Number(testResultKind);
        const testResultKey = DefaultReporter.getTestResultKindKey(testResultKind);
        testsOverview[testResultKey] += testResultOverview[key][testResultKind];
      });
    });
    summary.testsOverview = testsOverview;
    summary.testsOverview.percentOfPass = `${parseInt((summary.testsOverview.succeed / summary.overview.testCount) * 10000, 10) / 100}%`;
    summary.testsTotal = Object.values(testsOverview).reduce((total, cnt) => total + cnt, 0);
    // 覆盖率计算
    const {
      statementTotal,
      statementCovered,
      branchTotal,
      branchCovered,
      mcdcTotal,
      mcdcCovered,
      invokeTotal,
      invokeCovered,
      invokedFuncTotal,
      invokedFuncCovered,
    } = summary.overview;
    const {
      statementCoverage,
      branchCoverage,
      mcdcCoverage,
      invokeCoverage,
      invokedFuncCoverage,
    } = this.getCoverageOfFile({
      statementTotal,
      statementCovered,
      branchTotal,
      branchCovered,
      mcdcTotal,
      mcdcCovered,
      invokeTotal,
      invokeCovered,
      invokedFuncTotal,
      invokedFuncCovered,
    });

    summary.overview.statementCoverage = statementCoverage;
    summary.overview.branchCoverage = branchCoverage;
    summary.overview.mcdcCoverage = mcdcCoverage;
    summary.overview.invokeCoverage = invokeCoverage;
    summary.overview.invokedFuncCoverage = invokedFuncCoverage;
    summary.functions = files.reduce((arr, { functions }) => arr.concat(functions), []);
    summary.overview.testedFunctionCount = summary.functions.length;
    summary.overview.unTestedFunctionCount = summary.functions
      .reduce((cnt, func) => (func.summary.tested === false ? cnt + 1 : cnt), 0);

    files = files.map((f, index) => ({
      index: index + 1,
      ...f,
    }));

    let rowsCursor = files.length
      + files.reduce((funcCnt, file) => funcCnt + file.functions.length, 0)
      + summary.functions.reduce((testCnt, func) => testCnt + func.tests.length, 0) * 2;
    const points = [];
    let fileCursor = 0;
    // let totalRows = rowsCursor;
    for (let i = 0; i < files.length; i += 1) {
      const file = files[i];
      const rows = file.functions.reduce((total, func) => total + func.tests.length * 11, 0);
      // totalRows += rows;
      if (rowsCursor + rows <= rowsHighWaterMark) {
        rowsCursor += rows;
      } else {
        points.push([fileCursor, i]);
        rowsCursor = rows;
        fileCursor = i;
      }
      if (i === files.length - 1) {
        points.push([fileCursor, i + 1]);
      }
    }
    for (let i = 0; i < points.length; i += 1) {
      const [from, to] = points[i];
      const docx = i === 0 ? this.docx : new Docx(path.join(__dirname, 'template', 'docx', this.isIntegrationTest() ? '501-tail-it.docx' : '501-tail.docx'));
      const docxName = i === 0 ? 'report.docx' : `report${i}.docx`;
      // eslint-disable-next-line no-await-in-loop
      await docx.generate(
        {
          summary,
          files,
          filesLoop: files.slice(from, to),
        },
        path.join(
          this.outputDir,
          this.project.projectName,
          this.version.versionName,
          docxName
        ),
        true
      );
    }
  }

  /**
   * 处理单个文件，生成测试用例报告
   * @param {Object}} file 单个文件信息
   * @param {String} versionDir 版本目录
   * @returns {Promise}
   */
  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      super.handleFile(file, versionDir)
        .then((res) => {
          const { typeSystem, functions, resultCount } = res;
          if (!resultCount) {
            return resolve(undefined);
          }
          return resolve(this.generateFileDocxReport(file, typeSystem, functions));
        })
        .catch(reject);
    });
  }

  handleFiles(files, versionDir) {
    return Promise.all(files.map((file) => this.handleFile(file, versionDir)));
  }

  /**
   * 生成单个文件的docx报告
   * @param {String} reportPath 报告路径
   * @param {Object} file 文件信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Promise}
   */
  generateFileDocxReport(file, typeSystem, functions) {
    const coverage = this.getCoverageOfFile(common.getCountOfFile(file));
    return {
      coverage,
      fileName: file.fileName,
      summary: DefaultReporter.getSummaryOfFile(functions),
      functions: this.getFunctionDetailsOfFile(typeSystem, functions),
    };
  }

  /**
   * 获取单个文件概要统计
   * @param {Array} functions 函数列表
   * @returns {Object}
   */
  static getSummaryOfFile(functions) {
    const summary = {
      functionCount: functions.filter(({ kind }) => getCountableKinds().includes(kind)).length,
      tests: {
        unknown: 0,
        total: 0,
        succeed: 0,
        failed: 0,
        noResult: 0,
        exception: 0,
        codeDefect: 0,
      },
    };
    functions.forEach((func) => {
      summary.tests.total += func.tests.length;
      func.tests.forEach((test) => {
        const key = DefaultReporter.getTestResultKindKey(test.resultKind);
        test.resultKindName = key;
        summary.tests[key] += 1;
      });
    });
    return summary;
  }

  getCoverageOfFile({
    statementTotal, statementCovered,
    branchTotal, branchCovered,
    mcdcTotal, mcdcCovered,
    invokeTotal, invokeCovered,
    invokedFuncTotal, invokedFuncCovered,
  }) {
    const { coverages } = this.version;
    let statementCoverage;
    let branchCoverage;
    let mcdcCoverage;
    let invokeCoverage;
    let invokedFuncCoverage;
    if (statementTotal && (coverages & tests.coverageKind.statement) > 0) {
      statementCoverage = statementCovered / statementTotal;
      statementCoverage = `${Math.round(statementCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.statement) > 0) {
      statementCoverage = '-';
    }
    if (branchTotal && (coverages & tests.coverageKind.branch) > 0) {
      branchCoverage = branchCovered / branchTotal;
      branchCoverage = `${Math.round(branchCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.branch) > 0) {
      branchCoverage = '-';
    }
    if (mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
      mcdcCoverage = mcdcCovered / mcdcTotal;
      mcdcCoverage = `${Math.round(mcdcCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.mcdc) > 0) {
      mcdcCoverage = '-';
    }
    if (invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
      invokeCoverage = invokeCovered / invokeTotal;
      invokeCoverage = `${Math.round(invokeCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.invoke) > 0) {
      invokeCoverage = '-';
    }
    if (invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
      invokedFuncCoverage = invokedFuncCovered / invokedFuncTotal;
      invokedFuncCoverage = `${Math.round(invokedFuncCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
      invokedFuncCoverage = '-';
    }
    return {
      statementCoverage,
      branchCoverage,
      mcdcCoverage,
      invokeCoverage,
      invokedFuncCoverage,
    };
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    functions.sort((a, b) => {
      if (a.index < b.index) return -1;
      if (a.index > b.index) return 1;
      return 0;
    });
    functions.forEach((func) => {
      if (!func.tests.length) {
        return;
      }
      const tests = this.getTestDetailsOfFunction(typeSystem, func);
      const stubNames = func.variables && func.variables.stubs ? Object.keys(func.variables.stubs) : [];
      resFunctions.push({
        functionName: func.functionName,
        description: func.description,
        integrationFunctions: func.integrationFunctions && func.integrationFunctions.length > 0 ? func.integrationFunctions.map((f) => {
          if (f.name.indexOf('@') !== -1) {
            return handleInV.getOriginalName(f.name);
          }
          return f.name;
        }) : ['无'],
        summary: this.getSummaryOfFunction(func),
        firstTest: tests.length ? tests[0] : undefined,
        restTests: tests.length ? tests.slice(1) : [],
        tests,
        testCount: DefaultReporter.getTestCountOfFunction(func),
        stubs: stubNames.length > 0 ? stubNames.map((name) => {
          if (name.indexOf('@') !== -1) {
            return handleInV.getOriginalName(name);
          }
          return name;
        }) : ['无'],
      });
    });
    return resFunctions.map((item, idx) => ({
      ...item,
      index: idx + 1,
    }));
  }

  /**
   * 获取某个函数的概要统计
   * @param {Object}} func 函数信息
   * @returns {Object}
   */
  getSummaryOfFunction(func) {
    let returnType = 'void';
    if (func.variables.output && func.variables.output['%']) {
      returnType = func.variables.output['%']['@type'];
    }
    let { functionNameArgs } = func;
    if (!functionNameArgs) {
      const params = [];
      if (func.variables.variables && func.variables.variables.params) {
        Object.keys(func.variables.variables.params).forEach((paramName) => {
          params.push(`${func.variables.variables.params[paramName]['@type']} ${paramName}`);
        });
      }
      functionNameArgs = `${func.functionName}(${params.join(', ')})`;
    }
    const summary = {
      declaration: `${returnType} ${functionNameArgs};`,
      testCount: func.tests.length,
      tested: false,
    };
    const { coverages } = this.version;
    // eslint-disable-next-line no-bitwise
    if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = func.statementCovered / func.statementTotal;
      summary.statementCoverage = Math.round(summary.statementCoverage * 10000) / 100;
      if (func.statementCovered) {
        summary.tested = true;
      }
    } else if ((coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = func.branchCovered / func.branchTotal;
      summary.branchCoverage = Math.round(summary.branchCoverage * 10000) / 100;
      if (func.branchCovered) {
        summary.tested = true;
      }
    } else if ((coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = '-';
    }

    // eslint-disable-next-line no-bitwise
    if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
      summary.mcdcCoverage = Math.round(summary.mcdcCoverage * 10000) / 100;
      if (func.mcdcCovered) {
        summary.tested = true;
      }
    } else if ((coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = '-';
    }

    // eslint-disable-next-line no-bitwise
    if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = func.invokeCovered / func.invokeTotal;
      summary.invokeCoverage = Math.round(summary.invokeCoverage * 10000) / 100;
      if (func.invokeCovered) {
        summary.tested = true;
      }
    } else if ((coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = '-';
    }

    // eslint-disable-next-line no-bitwise
    if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = func.invokedFuncCovered / func.invokedFuncTotal;
      summary.invokedFuncCoverage = Math.round(summary.invokedFuncCoverage * 10000) / 100;
      if (func.invokedFuncCovered) {
        summary.tested = true;
      }
    } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = '-';
    }

    return summary;
  }

  /**
   * 获取用例列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} func 函数信息
   */
  getTestDetailsOfFunction(typeSystem, func) {
    const resTests = [];
    func.tests.sort((a, b) => {
      if (a.number < b.number) return -1; // eslint-disable-line no-underscore-dangle
      if (a.number > b.number) return 1; // eslint-disable-line no-underscore-dangle
      return 0;
    });
    const funcVariables = functions.getFunctionVariableJson(func.variables);
    if (!this.isIntegrationTest()) {
      typeSystem = files.getTypesJson(func.fileId, typeSystem);
    }
    func.tests.forEach((test) => {
      const testCase = tests.getJson(test, this.i18n);
      const transformer = new this.ReportTestCaseTransformer(
        testCase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      transformer.transform();
      const { coverages } = this.version;
      let statementCoverage;
      let branchCoverage;
      let mcdcCoverage;
      let invokeCoverage;
      let invokedFuncCoverage;
      if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = test.statementCovered / func.statementTotal;
        statementCoverage = `${Math.round(statementCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = '-';
      }
      if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = test.branchCovered / func.branchTotal;
        branchCoverage = `${Math.round(branchCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = '-';
      }
      if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = test.mcdcCovered / func.mcdcTotal;
        mcdcCoverage = `${Math.round(mcdcCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = '-';
      }
      if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = test.invokeCovered / func.invokeTotal;
        invokeCoverage = `${Math.round(invokeCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = '-';
      }
      if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = test.invokedFuncCovered / func.invokedFuncTotal;
        invokedFuncCoverage = `${Math.round(invokedFuncCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = '-';
      }
      let currentCat;
      const input = transformer.input.reduce((categories, item) => {
        if (item.category) {
          currentCat = categories[item.category] = categories[item.category] || {
            categoryName: item.category,
            items: [],
          };
          currentCat.items.push(item);
        } else {
          currentCat.items.push(item);
        }
        return categories;
      }, {});
      const output = transformer.output.reduce((categories, item) => {
        if (item.category) {
          currentCat = categories[item.category] = categories[item.category] || {
            categoryName: item.category,
            items: [],
          };
          currentCat.items.push(item);
        } else {
          currentCat.items.push(item);
        }
        return categories;
      }, {});
      resTests.push({
        index: test.number,
        description: testCase.description,
        resultKind: test.resultKindName,
        detail: {
          prelude: transformer.prelude,
          input,
          output,
          result: transformer.result,
        },
        statementCoverage,
        branchCoverage,
        mcdcCoverage,
        invokeCoverage,
        invokedFuncCoverage,
      });
    });
    return resTests;
  }

  static getTestCountOfFunction(func) {
    const testCount = {
      unknown: 0,
      total: 0,
      succeed: 0,
      failed: 0,
      noResult: 0,
      exception: 0,
      codeDefect: 0,
    };
    func.tests.forEach((test) => {
      const key = DefaultReporter.getTestResultKindKey(test.resultKind);
      testCount[key] += 1;
    });
    return testCount;
  }

  /**
   * 测试结果数字类型对应的名称
   * @param {Number} kind 测试结果类型
   * @returns {Object}
   */
  static getTestResultKindKey(kind) {
    switch (kind) {
      case tests.resultKind.unknown:
        return 'unknown';
      case tests.resultKind.succeed:
      case tests.resultKind.nonStrictSucceed:
        return 'succeed';
      case tests.resultKind.failed:
        return 'failed';
      case tests.resultKind.noResult:
        return 'noResult';
      case tests.resultKind.runtimeError:
      case tests.resultKind.timeout:
        return 'codeDefect';
      default:
        return 'exception';
    }
  }

  static versionTypeSupport(vType) {
    return vType !== projectVersions.versionType.staticAnalyze;
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
